using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace Ltf.Diffs
{
	public class Diff3Results
	{
		private const string ChangePattern = "^====[123]?$(?<first>[123]):(?<ffrom>[0-9]+),(?<fto>[0-9]+))[ac]$(?<second>[123]):(?<sfrom>[0-9]+),(?<sto>[0-9]+))[ac]$(?<third>[123]):(?<tfrom>[0-9]+),(?<tto>[0-9]+))[ac]$";
		public List<Diff3Hunk> Hunks;



		public Diff3Results(string diff3results)
		{
			string pattern = "====(?<diff>[123])?";
			string pattern2 =	"(?<fileno>[123]):(?<from>[0-9]+)(,(?<to>[0-9]+))?(?<type>[ac])";
			StringReader sr = new StringReader(diff3results);
			Regex startRegex = new Regex(pattern);
			Regex changeRegex = new Regex(pattern2);
			string line = null;
			Diff3Hunk change = null;
			Hunks = new List<Diff3Hunk>();

//			MatchCollection matches = changeRegex.Matches(diff3results);
			line = sr.ReadLine();
			do
			{
				if (line != null)
				{
					Match match = startRegex.Match(line);
					if (match.Success)
					{
						int hunktype = 0;
						int.TryParse(match.Groups["diff"].Value, out hunktype);
						change = new Diff3Hunk((Diff3HunkType)hunktype);
						Hunks.Add(change);
						List<string> lines = new List<string>();
						do
						{
							line = sr.ReadLine();
							if (line != null)
							{
								Match m2 = changeRegex.Match(line);
								if (m2.Success)
								{
									int fileno = int.Parse(m2.Groups["fileno"].Value);
									int from = int.Parse(m2.Groups["from"].Value);
									RangeType rangetype = (m2.Groups["type"].Value)=="a"?RangeType.Add:RangeType.Change;
									int to = 0;
									int.TryParse(m2.Groups["to"].Value, out to);
									lines = new List<string>();
									change.SetRange(fileno, from, to==0?from:to, lines, rangetype);
								}
								else
									if (!line.StartsWith("===="))
									{
										if (line.Length > 2)
											lines.Add(line.Substring(2));
										else
											lines.Add("");
									}
							}
						} while (line!=null && !line.StartsWith("===="));
						lines = new List<string>();
					}
					else
						line = sr.ReadLine();
				}
			} while (line != null);
		}


		public void GetHunkCounts(out int inserts, out int changes, out int deletes, out int conflicts,
			out int existingInserts, out int existingChanges, out int existingDeletes)
		{
			inserts = changes = deletes = conflicts = 0;
			existingInserts = existingChanges = existingDeletes = 0;
			foreach (Diff3Hunk dh in Hunks)
			{
				switch (dh.HunkType)
				{
					case Diff3HunkType.LocalDiff:
						break;
					case Diff3HunkType.BaseDiff:
						if (dh.BaseRange.IsAdd)
							existingDeletes++;
						else if (dh.BaseRange.IsChange && dh.ServerRange.IsChange)
							existingChanges++;
						else if (dh.BaseRange.IsChange && dh.ServerRange.IsAdd)
							existingDeletes++;
						break;
					case Diff3HunkType.ServerDiff:
						if (dh.ServerRange.IsAdd)
							deletes++;
						else if (dh.ServerRange.IsChange && dh.BaseRange.IsAdd)
							inserts++;
						else if (dh.ServerRange.IsChange && dh.BaseRange.IsChange)
							changes++;
						break;
					case Diff3HunkType.AllDiff:
						conflicts++;
						break;
					default:
						break;
				}
			}
		}



		public enum Diff3HunkType
		{
			AllDiff=0,
			LocalDiff=1,
			BaseDiff=2,
			ServerDiff=3
		}

		public class Diff3Hunk
		{
			public Diff3HunkType HunkType;
			private List<string> localLines;
			private List<string> baseLines;
			private List<string> serverLines;
			public Range LocalRange;
			public Range BaseRange;
			public Range ServerRange;

			public Diff3Hunk(Diff3HunkType hunkType)
			{
				HunkType = hunkType;
			}

			public Diff3Hunk(Diff3HunkType hunkType, Range localRange, Range baseRange, Range serverRange)
			{
				HunkType = hunkType;
				LocalRange = localRange;
				BaseRange = baseRange;
				ServerRange = serverRange;
				LocalLines = new List<string>();
				BaseLines = new List<string>();
				ServerLines = new List<string>();
			}

			public List<string> BaseLines
			{
				get
				{
					if (baseLines.Count == 0 && IsServerDiff)
						return localLines;
					else if (baseLines.Count == 0 && IsLocalDiff)
						return serverLines;
					else
						return baseLines;
				}
				set { baseLines = value; }
			}

			public List<string> LocalLines
			{
				get
				{
					if (localLines.Count == 0 && IsServerDiff)
						return baseLines;
					else if (localLines.Count == 0 && IsBaseDiff)
						return serverLines;
					else
						return localLines;
				}
				set { localLines = value; }
			}
			public List<string> ServerLines
			{
				get
				{
					if (serverLines.Count == 0 && IsLocalDiff)
						return baseLines;
					else if (serverLines.Count == 0 && IsBaseDiff)
						return localLines;
					else
						return serverLines;
				}
				set { serverLines = value; }
			}

			public void SetRange(int fileNo, int from, int to, List<string> lines, RangeType type)
			{
				Range r = new Range(from, to, type);
				switch (fileNo)
				{
					case 1:
						LocalRange = r;
						LocalLines = lines;
						break;
					case 2:
						BaseRange = r;
						BaseLines = lines;
						break;
					case 3:
						ServerRange = r;
						ServerLines = lines;
						break;
				}
			}

			public Diff3Hunk(int localStart, int localEnd, int baseStart, int baseEnd, int serverStart, int serverEnd)
				: this(new Range(localStart, localEnd) , new Range(baseStart, baseEnd), new Range(serverStart, serverEnd)) { }

			public Diff3Hunk(Range localRange, Range baseRange, Range serverRange)
			{
				LocalRange = localRange;
				BaseRange = baseRange;
				ServerRange = serverRange;
				LocalLines = new List<string>();
				BaseLines = new List<string>();
				ServerLines = new List<string>();
				// work out type here.
			}

			public bool IsLocalDiff
			{
				get { return HunkType == Diff3HunkType.LocalDiff; }
			}

			public bool IsServerDiff
			{
				get { return HunkType == Diff3HunkType.ServerDiff; }
			}

			public bool IsBaseDiff
			{
				get { return HunkType == Diff3HunkType.BaseDiff; }
			}

			public bool IsAllDiff
			{
				get { return HunkType == Diff3HunkType.AllDiff; }
			}

			public bool IsDeletion
			{
				get { return (IsLocalDiff && LocalLines.Count == 0) || (IsServerDiff && ServerLines.Count == 0); }
			}

		}

		public enum RangeType
		{
			None,
			Add,
			Change
		}


		public struct Range
		{
			public int From;
			public int To;
			public RangeType RangeType;

			public Range(string from, string to)
			{
				From = int.Parse(from);
				if (string.IsNullOrEmpty(to))
					To = From;
				else
					To = int.Parse(to);
				RangeType = RangeType.None;
			}

			public Range(int from, int to, RangeType type)
			{
				From = from;
				To = to;
				RangeType = type;
			}


			public Range(int from, int to)
			{
				From = from;
				To = to;
				RangeType = RangeType.None;
			}

			public Range(int from)
			{
				From = To = from;
				RangeType = RangeType.None;
			}
			public Range(string from)
			{
				From = To = int.Parse(from);
				RangeType = RangeType.None;
			}

			/// <summary>
			/// Gets or sets the interval between First changed line, and the first line following the change.
			/// </summary>
			/// <value>The interval.</value>
			public int Interval
			{
				get { return To - From + 1; }
				set { To = From + value - 1; }
			}

			public int LineCount
			{
				get 
				{
					if (RangeType == RangeType.Add)
						return 0;
					else
						return To - From + 1; 
				}
			}

			public bool HasType(RangeType type)
			{
				return RangeType == type;
			}

			public bool IsAdd
			{
				get { return RangeType == RangeType.Add; }
			}

			public bool IsChange
			{
				get { return RangeType == RangeType.Change; }
			}

		}
	}
}

